#define DOHOME_LOG_LVL          DOHOME_LOG_LVL_INFO
#define DOHOME_LOG_TAG          "dohome_hal_music"

#include <dohome_log.h>
#include <dohome_type.h>
#include <dohome_error_code.h>

#include "dohome_hal_mem.h"

#include "dohome_hal_musicdym.h"

DOHOME_STATIC DOHOME_UINT16_T _musicdym_run = 0;
DOHOME_STATIC DOHOME_UINT16_T _musicdym_initing = 0;

DOHOME_STATIC DOHOME_UINT16_T _adc_gpio = 5;
DOHOME_STATIC DOHOME_UINT32_T _sampling_freq = 8000;

DOHOME_STATIC DOHOME_UINT16_T _point_num = 0;
volatile DOHOME_UINT16_T _curr_read_point_cnt = 0;
DOHOME_STATIC dohome_music_point_t *_music_point = NULL;

DOHOME_STATIC DOHOME_UINT8_T _adc_channel[] = {0xff, 0xff, 0xff, 0xff, 1, 4, 5, 0xff, 0xff, 7, 8, 10, 0, 3, 2, 11, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};


static uint32_t *ad_buf = NULL;
//FFT
//==================================================================
#include "math.h"

typedef	struct														//定义一个复数结构
{
  float real;
  float imag;
}compx;
#define PI  3.141592653

compx *s_fft = NULL;                                              //FFT输入和输出：从S_fft[0]开始存放，根据大小自己定义
static float *SIN_TAB = NULL;
static int fft_npt = 0;

compx EE(compx a,compx b);
float sin_tab(float pi);
float cos_tab(float pi);

void FFT(compx *xin);
void create_sin_tab(float *sin_t);


/*******************************************************************
函数原型：compx EE(compx b1,compx b2)
函数功能：对两个复数进行乘法运算
输入参数：两个以联合体定义的复数a,b
输出参数：a和b的乘积，以联合体的形式输出
*******************************************************************/
compx EE(compx a,compx b)
{
 compx c;
 c.real=a.real*b.real-a.imag*b.imag;
 c.imag=a.real*b.imag+a.imag*b.real;
 return(c);
}

/******************************************************************
函数原型：void create_sin_tab(float *sin_t)
函数功能：创建一个正弦采样表，采样点数与福利叶变换点数相同
输入参数：*sin_t存放正弦表的数组指针
输出参数：无
******************************************************************/
void create_sin_tab(float *sin_t)
{
  int i;
  for(i=0;i<=fft_npt/4;i++)
  	sin_t[i]=sin(2*PI*i/fft_npt);
}
/******************************************************************
函数原型：void sin_tab(float pi)
函数功能：采用查表的方法计算一个数的正弦值
输入参数：pi 所要计算正弦值弧度值，范围0--2*PI，不满足时需要转换
输出参数：输入值pi的正弦值
******************************************************************/
float sin_tab(float pi)
{
  int n;
  float a=0;
  // while (pi > 2*PI){
  //   pi-=2*PI;
  // }
  
  n=(int)(pi*fft_npt/2/PI);

  if(n>=0&&n<=fft_npt/4)
    a=SIN_TAB[n];
  else if(n>fft_npt/4&&n<fft_npt/2)
    {
     n-=fft_npt/4;
     a=SIN_TAB[fft_npt/4-n];
    }
  else if(n>=fft_npt/2&&n<3*fft_npt/4)
    {
     n-=fft_npt/2;
     a=-SIN_TAB[n];
   }
  else if(n>=3*fft_npt/4&&n<3*fft_npt)
    {
     n=fft_npt-n;
     a=-SIN_TAB[n];
   }

  return a;
}
/******************************************************************
函数原型：void cos_tab(float pi)
函数功能：采用查表的方法计算一个数的余弦值
输入参数：pi 所要计算余弦值弧度值，范围0--2*PI，不满足时需要转换
输出参数：输入值pi的余弦值
******************************************************************/
float cos_tab(float pi)
{
   float a,pi2;
   pi2=pi+PI/2;
   if(pi2>2*PI)
     pi2-=2*PI;
   a=sin_tab(pi2);
   return a;
}
/*****************************************************************
函数原型：void FFT(compx *xin,int N)
函数功能：对输入的复数组进行快速傅里叶变换（FFT）
输入参数：*xin复数结构体组的首地址指针，struct型
输出参数：无
*****************************************************************/
void FFT(compx *xin)
{
  int f,m,nv2,nm1,i,k,l,j=0;
  compx u,w,t;

   nv2=fft_npt/2;                  //变址运算，即把自然顺序变成倒位序，采用雷德算法
   nm1=fft_npt-1;
   for(i=0;i<nm1;i++)
   {
    if(i<j)                    //如果i<j,即进行变址
     {
      t=xin[j];
      xin[j]=xin[i];
      xin[i]=t;
     }
    k=nv2;                    //求j的下一个倒位序
    while(k<=j)               //如果k<=j,表示j的最高位为1
     {
      j=j-k;                 //把最高位变成0
      k=k/2;                 //k/2，比较次高位，依次类推，逐个比较，直到某个位为0
     }
   j=j+k;                   //把0改为1
  }

  {
   int le,lei,ip;                            //FFT运算核，使用蝶形运算完成FFT运算
    f=fft_npt;
   for(l=1;(f=f/2)!=1;l++)                  //计算l的值，即计算蝶形级数
           ;
  for(m=1;m<=l;m++)                         // 控制蝶形结级数
   {                                        //m表示第m级蝶形，l为蝶形级总数l=log（2）N
    le=2<<(m-1);                            //le蝶形结距离，即第m级蝶形的蝶形结相距le点
    lei=le/2;                               //同一蝶形结中参加运算的两点的距离
    u.real=1.0;                             //u为蝶形结运算系数，初始值为1
    u.imag=0.0;
    //w.real=cos(PI/lei);                  //不适用查表法计算sin值和cos值
    // w.imag=-sin(PI/lei);
    w.real=cos_tab(PI/lei);                //w为系数商，即当前系数与前一个系数的商
    w.imag=-sin_tab(PI/lei);
    for(j=0;j<=lei-1;j++)                  //控制计算不同种蝶形结，即计算系数不同的蝶形结
     {
      for(i=j;i<=fft_npt-1;i=i+le)           //控制同一蝶形结运算，即计算系数相同蝶形结
       {
        ip=i+lei;                          //i，ip分别表示参加蝶形运算的两个节点
        t=EE(xin[ip],u);                   //蝶形运算，详见公式
        xin[ip].real=xin[i].real-t.real;
        xin[ip].imag=xin[i].imag-t.imag;
        xin[i].real=xin[i].real+t.real;
        xin[i].imag=xin[i].imag+t.imag;
       }
      u=EE(u,w);                          //改变系数，进行下一个蝶形运算
     }
   }
  }
}


/*****************************************************************************
 * 文件名: sendwave.c
 *   版本: V1.2
 *   作者: 官文亮
 *   日期: 2018/9/2
 *   说明: 本文件属于SerialTool软件的波形显示功能的下位机参考代码, 作用是将数
 *         值转换为SerialTool可以识别的帧, 用户需实现串口发送函数, 结合本程序
 *         即可实现串口发送波形的显示, 本程序适合SerialTool v1.1.6及后续版本.
 *
 * SerialTool源码链接: https://github.com/gztss/SerialTool
 * SerialTool安装包链接: https://github.com/gztss/SerialTool/releases
 *
 *****************************************************************************/


#define EN_SERIAL_PUT       0

extern int bl_uart_data_send(uint8_t id, uint8_t data);
#define ws_putchar(c)          bl_uart_data_send(0, (char)c)

/* 定义各通道 */
#define     CH1     0
#define     CH2     1
#define     CH3     2
#define     CH4     3
#define     CH5     4
#define     CH6     5
#define     CH7     6
#define     CH8     7
#define     CH9     8
#define     CH10    9
#define     CH11    10
#define     CH12    11
#define     CH13    12
#define     CH14    13
#define     CH15    14
#define     CH16    15

/* 信息帧数据结构定义 */
typedef struct {
    uint8_t year;           // 0~99 -> 2000 ~ 2099, 7 bit
    uint8_t month;          // 1 ~ 12, 4 bit
    uint8_t day;            // 1 ~ 31, 5 bit
    uint8_t hour;           // 0 ~ 23, 5 bit
    uint8_t min;            // 0 ~ 59, 6 bit
    uint8_t sec;            // 0 ~ 59, 6 bit
    uint16_t msec;          // 0 ~ 999, 10 bit
    uint32_t sampleRate;    // 0 ~ 2000000, 21 bit
} ws_timestamp_t;

/* 公共函数声明 */
char ws_point_int8(char *buffer, char channel, int8_t value);
char ws_point_int16(char *buffer, char channel, int16_t value);
char ws_point_int32(char *buffer, char channel, int32_t value);
char ws_point_float(char *buffer, char channel, float value);
void ws_frame_init(char *buffer);
char ws_frame_length(const char *buffer);
char ws_add_int8(char *buffer, char channel, int8_t value);
char ws_add_int16(char *buffer, char channel, int16_t value);
char ws_add_int32(char *buffer, char channel, int32_t value);
char ws_add_float(char *buffer, char channel, float value);
char ws_send_timestamp(char *buffer, ws_timestamp_t* ts);

void ws_send_buffer(char *buffer);


/* 此处定义一些常量, 请勿修改! */
enum {
    Ch_Num          = 16,       // 通道数量
    Frame_MaxBytes  = 80,       // 最大帧长度
    Frame_Head      = 0xA3,     // 帧头识别字
    Frame_PointMode = 0xA8,     // 点模式识别字
    Frame_SyncMode  = 0xA9,     // 同步模式识别字
    Frame_InfoMode  = 0xAA,     // 信息帧识别字
    Format_Int8     = 0x10,     // int8识别字
    Format_Int16    = 0x20,     // int16识别字
    Format_Int32    = 0x30,     // int32识别字
    Format_Float    = 0x00      // float识别字
};

/* 函数功能: 发送int8类型数据
 * 函数参数:
 *     buffer : 帧缓冲区, 需要4byte
 *     channel: 通道, 取值范围为0~15
 *     value  : 通道数据值, 8bit有符号整数
 *     返回值 : 数据帧长度(单位为byte)
 **/
char ws_point_int8(char *buffer, char channel, int8_t value)
{
    if ((uint8_t)channel < Ch_Num) { // 通道验证
        // 帧头
        *buffer++ = Frame_Head;
        *buffer++ = Frame_PointMode;
        *buffer++ = channel | Format_Int8; // 通道及数据格式信息
        *buffer = value; // 数据添加到帧
        return 4; // 数据帧长度
    }
    return 0;
}

/* 函数功能: 发送int16类型数据
 * 函数参数:
 *     buffer : 帧缓冲区, 需要5byte
 *     channel: 通道, 取值范围为0~15
 *     value  : 通道数据值, 16bit有符号整数
 *     返回值 : 数据帧长度(单位为byte)
 **/
char ws_point_int16(char *buffer, char channel, int16_t value)
{
    if ((uint8_t)channel < Ch_Num) { // 通道验证
        // 帧头
        *buffer++ = Frame_Head;
        *buffer++ = Frame_PointMode;
        *buffer++ = channel | Format_Int16; // 通道及数据格式信息
        // 数据添加到帧
        *buffer++ = (value >> 8) & 0xFF;
        *buffer = value & 0xFF;
        return 5; // 数据帧长度
    }
    return 0;
}

/* 函数功能: 发送int32类型数据
 * 函数参数:
 *     buffer : 帧缓冲区, 需要7byte
 *     channel: 通道, 取值范围为0~15
 *     value  : 通道数据值, 32bit有符号整数
 *     返回值 : 数据帧长度(单位为byte)
 **/
char ws_point_int32(char *buffer, char channel, int32_t value)
{
    if ((uint8_t)channel < Ch_Num) { // 通道验证
        // 帧头
        *buffer++ = Frame_Head;
        *buffer++ = Frame_PointMode;
        *buffer++ = channel | Format_Int32; // 通道及数据格式信息
        // 数据添加到帧
        *buffer++ = (value >> 24) & 0xFF;
        *buffer++ = (value >> 16) & 0xFF;
        *buffer++ = (value >> 8) & 0xFF;
        *buffer = value & 0xFF;
        return 7; // 数据帧长度
    }
    return 0;
    
}

/* 函数功能: 发送float类型数据
 * 函数参数:
 *     buffer : 帧缓冲区, 需要7byte
 *     channel: 通道, 取值范围为0~15
 *     value  : 通道数据值, 类型为单精度浮点(32bit)
 *     返回值 : 数据帧长度(单位为byte)
 **/
char ws_point_float(char *buffer, char channel, float value)
{
    // 这个联合变量用来实现浮点到整形的变换
    union {
        float f;
        uint32_t i;
    } temp;

    if ((uint8_t)channel < Ch_Num) { // 通道验证
        temp.f = value;
        // 帧头
        *buffer++ = Frame_Head;
        *buffer++ = Frame_PointMode;
        *buffer++ = channel | Format_Float; // 通道及数据格式信息
        // 数据添加到帧
        *buffer++ = (temp.i >> 24) & 0xFF;
        *buffer++ = (temp.i >> 16) & 0xFF;
        *buffer++ = (temp.i >>  8) & 0xFF;
        *buffer = temp.i & 0xFF;
        return 7; // 数据帧长度
    }
    return 0;
}

/* 函数功能: 同步发送模式缓冲区初始化
 * 函数参数:
 *     buffer : 帧缓冲区, 最多需要(Frame_MaxBytes + 3) bytes
 **/
void ws_frame_init(char *buffer)
{
    *buffer++ = Frame_Head;
    *buffer++ = Frame_SyncMode;
    *buffer = 0;
}

/* 函数功能: 获取同步模式缓冲区长度(单位bytes)
 * 函数参数:
 *     buffer : 同步模式帧缓冲区
 **/
char ws_frame_length(const char *buffer)
{
    return buffer[2] + 3;
}

/* 函数功能: 在数同步据帧中加入一个int8类型数据
 * 函数参数:
 *     buffer : 已经初始化的帧缓冲区
 *     channel: 通道, 取值范围为0~15
 *     value  : 通道数据值, 类型为int8
 *     返回值 : 0, 加入成功, 1, 帧长度已经达到上限或通道错误
 **/
char ws_add_int8(char *buffer, char channel, int8_t value)
{
    char count = buffer[2];
    char *p = buffer + count + 3; // 跳过前面数据

    count += 2;
    // 帧长度及通道验证
    if (count <= Frame_MaxBytes && (uint8_t)channel < Ch_Num) {
        buffer[2] = count;
        *p++ = channel | Format_Int8; // 通道及数据格式信息
        *p = value; // 数据添加到帧
        return 1;
    }
    return 0;
}

/* 函数功能: 在数同步据帧中加入一个int16类型数据
 * 函数参数:
 *     buffer : 已经初始化的帧缓冲区
 *     channel: 通道, 取值范围为0~15
 *     value  : 通道数据值, 类型为int16
 *     返回值 : 0, 加入成功, 1, 帧长度已经达到上限或通道错误
 **/
char ws_add_int16(char *buffer, char channel, int16_t value)
{
    char count = buffer[2];
    char *p = buffer + count + 3; // 跳过前面数据

    count += 3;
    // 帧长度及通道验证
    if (count <= Frame_MaxBytes && (uint8_t)channel < Ch_Num) {
        buffer[2] = count;
        *p++ = channel | Format_Int16; // 通道及数据格式信息
        // 数据添加到帧
        *p++ = (value >> 8) & 0xFF;
        *p = value & 0xFF;
        return 1;
    }
    return 0;
}

/* 函数功能: 在数同步据帧中加入一个int32类型数据
 * 函数参数:
 *     buffer : 已经初始化的帧缓冲区
 *     channel: 通道, 取值范围为0~15
 *     value  : 通道数据值, 类型为int32
 *     返回值 : 0, 加入成功, 1, 帧长度已经达到上限或通道错误
 **/
char ws_add_int32(char *buffer, char channel, int32_t value)
{
    char count = buffer[2];
    char *p = buffer + count + 3; // 跳过前面数据

    count += 5;
    // 帧长度及通道验证
    if (count <= Frame_MaxBytes && (uint8_t)channel < Ch_Num) {
        buffer[2] = count;
        *p++ = channel | Format_Int32; // 通道及数据格式信息
        // 数据添加到帧
        *p++ = (value >> 24) & 0xFF;
        *p++ = (value >> 16) & 0xFF;
        *p++ = (value >> 8) & 0xFF;
        *p = value & 0xFF;
        return 1;
    }
    return 0;
}

/* 函数功能: 在数同步据帧中加入一个float类型数据
 * 函数参数:
 *     buffer : 已经初始化的帧缓冲区
 *     channel: 通道, 取值范围为0~15
 *     value  : 通道数据值, 类型为float
 *     返回值 : 0, 加入成功, 1, 帧长度已经达到上限或通道错误
 **/
char ws_add_float(char *buffer, char channel, float value)
{
    char count = buffer[2];
    char *p = buffer + count + 3; // 跳过前面数据

    count += 5;
    // 帧长度及通道验证
    if (count <= Frame_MaxBytes && (uint8_t)channel < Ch_Num) {
        union {
            float f;
            uint32_t i;
        } temp;
        buffer[2] = count;
        temp.f = value;
        *p++ = channel | Format_Float; // 通道及数据格式信息
        // 数据添加到帧
        *p++ = (temp.i >> 24) & 0xFF;
        *p++ = (temp.i >> 16) & 0xFF;
        *p++ = (temp.i >>  8) & 0xFF;
        *p = temp.i & 0xFF;
        return 1;
    }
    return 0;
}

/* 函数功能: 发送时间戳
 * 函数参数:
 *     buffer : 帧缓冲区
 *     ts     : 时间戳
 *     返回值 : 数据帧长度, (单位: bytes)
 **/
char ws_send_timestamp(char *buffer, ws_timestamp_t* ts)
{
    uint8_t temp;

    *buffer++ = Frame_Head;
    *buffer++ = Frame_InfoMode;
    temp = (ts->year << 1) | ((ts->month >> 3) & 0x01);
    *buffer++ = (char)temp;
    temp = (ts->month << 5) | (ts->day & 0x1F);
    *buffer++ = (char)temp;
    temp = (ts->hour << 3) | ((ts->min >> 3) & 0x07);
    *buffer++ = (char)temp;
    temp = (ts->min << 5) | ((ts->sec >> 1) & 0x1F);
    *buffer++ = (char)temp;
    temp = (ts->sec << 7) | ((ts->msec >> 3) & 0x7F);
    *buffer++ = (char)temp;
    temp = (ts->msec << 5) | ((ts->sampleRate >> 16) & 0x1F);
    *buffer++ = (char)temp;
    *buffer++ = (char)((ts->sampleRate >> 8) & 0xFF);
    *buffer = (char)(ts->sampleRate & 0xFF);
    return 10;
}

void ws_send_buffer(char *buffer){

    for (int i = 0; i < ws_frame_length(buffer); i++)
    {
        ws_putchar(buffer[i]);
    }
}

//adc
//===============================================================================

static int adc_npt = 0;
static void *adc_buf = NULL;

void adc_data_callback(int mode, uint16_t *ptr, uint32_t data_size)
{
    int i = 0;
    
    if (_musicdym_run == 0) {
        return;
    }
    
    if(_curr_read_point_cnt == 0){
        for (i = 0; i < data_size; i++){
            ad_buf[i] = ptr[i]/8;
            // printf("%d \n", ptr[i]);
            // char serial_buffer[83];
            // ws_frame_init(serial_buffer);
            // ws_add_int32(serial_buffer, CH2, ad_buf[i]);
            // ws_send_buffer(serial_buffer);
        }
        _curr_read_point_cnt = _point_num;
    }
}

DOHOME_STATIC DOHOME_FLOAT_T InvSqrt(DOHOME_FLOAT_T x)
{
    DOHOME_FLOAT_T xhalf = 0.5f * x;
    DOHOME_INT_T i = *(DOHOME_INT_T*)&x;
    i = 0x5f375a86 - (i>>1);
    x = *(DOHOME_FLOAT_T*)&i;
    x = x*(1.5f-xhalf*x*x);
    x = x*(1.5f-xhalf*x*x);
    x = x*(1.5f-xhalf*x*x);
    return 1/x;
}


DOHOME_STATIC dohome_op_ret fft_init(int npt){

    fft_npt = npt;

    s_fft = dohome_hal_mem_alloc(sizeof(compx)*fft_npt);
    if(s_fft == NULL){
        return OPRT_COM_ERROR;
    }

    SIN_TAB = dohome_hal_mem_alloc(sizeof(float)*fft_npt/4+1);
    if(SIN_TAB == NULL){
        dohome_hal_mem_free(s_fft);
        return OPRT_COM_ERROR;
    }
    create_sin_tab(SIN_TAB);

    // int i = 0;
    // for(i=0; i<fft_npt/4+1; i++)
    // {
    //     DOHOME_LOG_D("sin_tab%d: %f", i, SIN_TAB[i]);
    // }
    return OPRT_OK;
}


DOHOME_STATIC DOHOME_UINT8_T adc_is_init = 0;
DOHOME_UINT8_T dohome_adc_is_init(void){
    return adc_is_init;
}

dohome_op_ret dohome_adc_init(int npt){
    int ret = 0;
    
    
    return OPRT_OK;
}


dohome_op_ret dohome_hal_musicdym_set_adc(DOHOME_UINT16_T gpio, DOHOME_UINT16_T freq){
    if(_adc_channel[gpio] ==0xff){
        DOHOME_LOG_E("gpio: %d not support adc", gpio);
        return OPRT_COM_ERROR;
    }
    _adc_gpio = gpio;
    _sampling_freq = freq;
    return OPRT_OK;
}

dohome_op_ret dohome_hal_musicdym_init(DOHOME_UINT16_T point_num)
{
    DOHOME_LOG_I("dohome_hal_musicdym_init");
    _point_num = point_num * 2;
    _music_point = dohome_hal_mem_alloc(sizeof(dohome_music_point_t)*_point_num);
    if(_music_point == NULL){
        return OPRT_COM_ERROR;
    }

    ad_buf = dohome_hal_mem_alloc(sizeof(uint32_t)*_point_num);
    if(ad_buf == NULL){
        return OPRT_COM_ERROR;
    }

    if(fft_init(_point_num) != OPRT_OK){
        return OPRT_COM_ERROR;
    }

    dohome_adc_init(_point_num);

    _musicdym_initing = 1;
    DOHOME_LOG_I("dohome_hal_musicdym_init ok");
    return OPRT_OK;
}
// dohome_hal_musicdym_read 属于单次查询。如果有数据请立即返回数据并返回dohome_music_state_ok，否则返回dohome_music_state_busy。
dohome_music_state_t dohome_hal_musicdym_read_all(dohome_music_point_t *point_list)
{
    DOHOME_UINT16_T i = 0;
    if(_curr_read_point_cnt == _point_num){
        // DOHOME_LOG_D("dohome_hal_musicdym_read_all ok");
        for (i = 0; i < _point_num; i++)
        {
            // DOHOME_LOG_D("adc_buf: %d", ad_buf[i]);
            s_fft[i].real = ((DOHOME_FLOAT_T)(ad_buf[i]/fft_npt/1.0));
            // DOHOME_LOG_D("real: %d", (DOHOME_INT_T)s_fft[i].real);
            s_fft[i].imag = 0;
        }

        FFT(s_fft); 

        for(i=0; i<fft_npt/2; i++)
        {
            _music_point[i].amplitude = (DOHOME_INT_T)InvSqrt(s_fft[i].real*s_fft[i].real +s_fft[i].imag*s_fft[i].imag);
            if(_music_point[i].amplitude<0) _music_point[i].amplitude=0;
            // _music_point[i].amplitude = _music_point[i].amplitude-(_music_point[i].amplitude)/4;
        }
        _music_point[0].amplitude = 0x00;//消除第一个

        memcpy(point_list, _music_point, sizeof(dohome_music_point_t)*_point_num/2);
        return DOHOME_MUSIC_STATE_OK;
    }else{
        return DOHOME_MUSIC_STATE_BUSY;
    }
}

void dohome_hal_musicdym_reset(void)
{
    DOHOME_LOG_I("dohome_hal_musicdym_reset ok");
    _curr_read_point_cnt = 0;
}

void dohome_hal_musicdym_start(void)
{
    DOHOME_LOG_I("dohome_hal_musicdym_start");

}

void dohome_hal_musicdym_stop(void)
{
    DOHOME_LOG_I("dohome_hal_musicdym_stop");

}
